package com.owc.common.lock;

import com.alibaba.fastjson.JSON;
import com.owc.common.Const.MatchConst;
import com.owc.common.enums.LockType;
import com.owc.common.tool.UtilsService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author xiaom
 * @Date 2020/3/13 13:47
 * @Version 1.0.0
 * @Description <>
 **/
@Aspect
@Component
@Slf4j
public class LockAspect {

    private static ThreadLocal<Lock> lockCache = new ThreadLocal<>();
    /**
     * Method pointcut.
     */
    @Pointcut("@annotation(com.owc.common.lock.ULock)")
    public void pointCut() {}


    @After("pointCut()")
    public void unLock(JoinPoint point){
        lockCache.get().unLock();
        lockCache.remove();
    }


    @Before("pointCut()")
    public void lockCheck(JoinPoint joinPoint){
        lockCache.set(getLock(getLockAnnotation(joinPoint)));
        lockCache.get().lock(getLockKey(joinPoint));
    }

    private Lock getLock(ULock lock){
        return new Lock(lock.maxTimeout(),lock.awaitTimeout());
    }

    private String getLockKey(JoinPoint joinPoint){
        //Fetch lock annotation.
        ULock uLock = getLockAnnotation(joinPoint);
        try {
            //Parser lock mode.
            LockType lockType = parserType(uLock);
            Assert.notNull(lockType,"Parser lock type failed. lockKey:[" + uLock.lockKey() + "]");

            //Analyze annotation parameters dynamically
            List<String> expressions = parseLockAnnotationKey(uLock,lockType);
            log.info("Lock annotation expressions : [{}]", JSON.toJSONString(expressions));

            LockModel lockModel = getProxyModel(joinPoint);
            lockModel.setExpression(expressions);

            //Get lock real value.
            String lockPrefix = lockType.getActuator().doExecute(lockModel);
            log.info("Lock actual value:[{}] Lock type:[{}]",lockPrefix,lockType.name());
            return lockPrefix;
        } catch (Exception e) {
            log.error("Lock before throw error. Message:[{}]",e.getMessage());
            throw e;
        }
    }

    private ULock getLockAnnotation(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method targetMethod = methodSignature.getMethod();
        //Fetch lock annotation.
        return targetMethod.getAnnotation(ULock.class);
    }


    /**
     * Judge type.
     */
    private LockType parserType(ULock uLock){
        String lockValue = uLock.lockKey();
        if(lockValue.isEmpty()){
            return LockType.DEFAULT_TYPE;
        }
        if(UtilsService.stringExtract(lockValue, MatchConst._PATTERN_M) != null){
            return LockType.EXPRESSION_TYPE;
        }
        if(UtilsService.stringExtract(lockValue,MatchConst._PATTERN_$) != null){
            return LockType.EXPRESSION_ORIGINAL_TYPE;
        }
        return null;
    }

    /**
     * Parsing expressions in annotations
     * @param uLock annotation
     * @return expressions
     */
    private List<String> parseLockAnnotationKey(ULock uLock,LockType lockType) {
        switch (lockType){
            case EXPRESSION_TYPE:
                return parserExpressionKeyByPattern(uLock,MatchConst._PATTERN_M);
            case EXPRESSION_ORIGINAL_TYPE:
                return parserExpressionKeyByPattern(uLock,MatchConst._PATTERN_$);
            default:
                return null;
        }
    }

    private List<String> parserExpressionKeyByPattern(ULock uLock,String pattern){
        String lockValue = uLock.lockKey();
        //Params lock value expression
        String[] values = lockValue.split(",");

        List<String> valList = new ArrayList<>();

        for (String value : values) {
            String val = UtilsService.stringExtract(value,pattern);
            if(val == null) continue;
            valList.add(val);
        }
        return valList;
    }

    /**
     * Get proxy method args.
     */
    private LockModel getProxyModel(JoinPoint joinPoint){
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        return new LockModel(joinPoint.getArgs(),null,methodSignature.getParameterNames(),methodSignature.getMethod());
    }
}
